﻿namespace Microsoft.SharePoint.Client
{
    using System;
    using System.Collections.Generic;
    using System.Linq.Expressions;
    using System.Xml;

    internal class ClientQueryInternal : ClientAction, IEquatable<ClientQueryInternal>
    {
        private ChunkStringBuilder m_childItemFilterSb;
        private SerializationContext m_childItemFilterSerializationContext;
        private ClientQueryInternal m_childItemQuery;
        private ClientRuntimeContext m_context;
        private bool m_isChildItemQuery;
        private Dictionary<string, ClientQueryProperty> m_properties;
        private ClientQueryInternal m_rootQuery;
        private bool m_selectAllProperties;
        private List<ClientQueryInternal> m_subQueries;

        public ClientQueryInternal(ClientObject obj, string name, bool subQuery, ClientQueryInternal parentQuery) : base(subQuery ? null : obj.Path, name)
        {
            this.m_properties = new Dictionary<string, ClientQueryProperty>();
            this.m_subQueries = new List<ClientQueryInternal>();
            if (!subQuery && ((obj.Path == null) || !obj.Path.IsValid))
            {
                throw new ClientRequestException(Resources.GetString("NoObjectPathAssociatedWithObject"));
            }
            if (subQuery)
            {
                if (parentQuery == null)
                {
                    throw new ArgumentNullException("parentQuery");
                }
                this.m_rootQuery = parentQuery.RootQuery;
                this.m_context = parentQuery.Context;
            }
            else
            {
                if (obj == null)
                {
                    throw new ArgumentNullException("obj");
                }
                this.m_rootQuery = this;
                this.m_context = obj.Context;
            }
        }

        internal ClientQueryInternal ChildItemFilterExpression(ChunkStringBuilder childItemFilterExpression)
        {
            if (!this.m_isChildItemQuery)
            {
                throw new InvalidOperationException();
            }
            if (childItemFilterExpression == null)
            {
                throw new ArgumentOutOfRangeException("childItemFilterExpression");
            }
            if (this.m_childItemFilterSb != null)
            {
                throw new InvalidQueryExpressionException();
            }
            this.m_childItemFilterSb = childItemFilterExpression;
            return this;
        }

        public bool Equals(ClientQueryInternal query)
        {
            if (query == null)
            {
                return false;
            }
            return (base.Id == query.Id);
        }

        public override bool Equals(object obj)
        {
            ClientQueryInternal query = obj as ClientQueryInternal;
            return this.Equals(query);
        }

        public override int GetHashCode()
        {
            return base.Id.GetHashCode();
        }

        internal ClientQueryInternal GetSubQuery(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }
            ClientQueryProperty property = null;
            if (this.m_properties.TryGetValue(name, out property))
            {
                return property.Query;
            }
            return null;
        }

        public ClientQueryInternal Select(string propertyName)
        {
            if (string.IsNullOrEmpty(propertyName))
            {
                throw new ArgumentException();
            }
            ClientQueryProperty property = null;
            if (!this.m_properties.TryGetValue(propertyName, out property))
            {
                property = new ClientQueryProperty();
                this.m_properties[propertyName] = property;
            }
            if (property.Query != null)
            {
                throw new ArgumentOutOfRangeException("propertyName");
            }
            property.ScalarProperty = true;
            property.ScalarPropertySet = true;
            return this;
        }

        public ClientQueryInternal SelectAllProperties()
        {
            this.m_selectAllProperties = true;
            return this;
        }

        public ClientQueryInternal SelectSubQuery(ClientQueryInternal subQuery)
        {
            if (string.IsNullOrEmpty(subQuery.Name))
            {
                throw new ArgumentException();
            }
            ClientQueryProperty property = null;
            if (this.m_properties.TryGetValue(subQuery.Name, out property))
            {
                if (property.ScalarPropertySet && property.ScalarProperty)
                {
                    throw new ArgumentException();
                }
                if ((property.Query != null) && (property.Query != subQuery))
                {
                    throw new ArgumentException();
                }
                property.Query = subQuery;
            }
            else
            {
                property = new ClientQueryProperty {
                    Query = subQuery
                };
                this.m_properties[subQuery.Name] = property;
            }
            return this;
        }

        public ClientQueryInternal SelectWithAll(string propertyName)
        {
            if (string.IsNullOrEmpty(propertyName))
            {
                throw new ArgumentException();
            }
            ClientQueryProperty property = null;
            if (!this.m_properties.TryGetValue(propertyName, out property))
            {
                property = new ClientQueryProperty();
                this.m_properties[propertyName] = property;
            }
            property.SelectAll = true;
            property.SelectAllSet = true;
            return this;
        }

        internal void SetIsChildItemQuery()
        {
            this.m_isChildItemQuery = true;
        }

        internal static void WriteFilterExpressionToXml(XmlWriter writer, SerializationContext serializationContext, LambdaExpression childItemFilterExpression)
        {
            writer.WriteStartElement("Test");
            writer.WriteStartElement("Parameters");
            writer.WriteStartElement("Parameter");
            writer.WriteAttributeString("Name", childItemFilterExpression.Parameters[0].Name);
            writer.WriteEndElement();
            writer.WriteEndElement();
            writer.WriteStartElement("Body");
            Dictionary<string, Type> allowedParameters = new Dictionary<string, Type>();
            allowedParameters.Add(childItemFilterExpression.Parameters[0].Name, childItemFilterExpression.Parameters[0].Type);
            new ConditionalExpressionToXmlConverter(childItemFilterExpression.Body, writer, allowedParameters, serializationContext).Convert();
            writer.WriteEndElement();
            writer.WriteEndElement();
        }

        internal void WriteInnerXmlCoreTo(XmlWriter writer, SerializationContext serializationContext)
        {
            writer.WriteAttributeString("SelectAllProperties", this.m_selectAllProperties ? "true" : "false");
            writer.WriteStartElement("Properties");
            foreach (string str in this.m_properties.Keys)
            {
                ClientQueryProperty property = this.m_properties[str];
                writer.WriteStartElement("Property");
                writer.WriteAttributeString("Name", str);
                if (property.ScalarPropertySet)
                {
                    writer.WriteAttributeString("ScalarProperty", property.ScalarProperty ? "true" : "false");
                }
                if (property.SelectAllSet)
                {
                    writer.WriteAttributeString("SelectAll", property.SelectAll ? "true" : "false");
                }
                if (property.Query != null)
                {
                    property.Query.WriteInnerXmlTo(writer, serializationContext);
                }
                writer.WriteEndElement();
            }
            writer.WriteEndElement();
            if (this.m_isChildItemQuery && (this.m_childItemFilterSb != null))
            {
                writer.WriteStartElement("QueryableExpression");
                this.m_childItemFilterSb.WriteContentAsRawXml(writer);
                writer.WriteEndElement();
                if (this.m_childItemFilterSerializationContext != null)
                {
                    serializationContext.AddObjectPathsFrom(this.m_childItemFilterSerializationContext);
                }
            }
        }

        internal void WriteInnerXmlTo(XmlWriter writer, SerializationContext serializationContext)
        {
            writer.WriteStartElement("Query");
            this.WriteInnerXmlCoreTo(writer, serializationContext);
            writer.WriteEndElement();
            if (this.m_childItemQuery != null)
            {
                writer.WriteStartElement("ChildItemQuery");
                this.m_childItemQuery.WriteInnerXmlCoreTo(writer, serializationContext);
                writer.WriteEndElement();
            }
        }

        internal override void WriteToXml(XmlWriter writer, SerializationContext serializationContext)
        {
            writer.WriteStartElement("Query");
            writer.WriteAttributeString("Id", base.Id.ToString());
            writer.WriteAttributeString("ObjectPathId", base.Path.Id.ToString());
            serializationContext.AddObjectPath(base.Path);
            this.WriteInnerXmlTo(writer, serializationContext);
            writer.WriteEndElement();
        }

        internal SerializationContext ChildItemExpressionSerializationContext
        {
            get
            {
                if (this.m_childItemFilterSerializationContext == null)
                {
                    this.m_childItemFilterSerializationContext = new SerializationContext();
                }
                return this.m_childItemFilterSerializationContext;
            }
        }

        internal bool ChildItemFilterSet
        {
            get
            {
                return (this.m_childItemFilterSb != null);
            }
        }

        public ClientQueryInternal ChildItemQuery
        {
            get
            {
                if (this.m_childItemQuery == null)
                {
                    this.m_childItemQuery = new ClientQueryInternal(null, "_Child_Items_", true, this);
                    this.m_childItemQuery.SetIsChildItemQuery();
                }
                return this.m_childItemQuery;
            }
        }

        internal ClientRuntimeContext Context
        {
            get
            {
                return this.m_context;
            }
        }

        internal bool HasChildItemQuery
        {
            get
            {
                return (this.m_childItemQuery != null);
            }
        }

        public bool IsChildItemQuery
        {
            get
            {
                return this.m_isChildItemQuery;
            }
        }

        internal ClientQueryInternal RootQuery
        {
            get
            {
                return this.m_rootQuery;
            }
        }
    }
}

